package org.springframework.http;

import org.springframework.http.ContentDisposition.Builder;
import org.springframework.lang.Nullable;
import org.springframework.util.*;

import java.io.Serializable;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.Locale.LanguageRange;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class HttpHeaders implements MultiValueMap<String, String>, Serializable {
    private static final long serialVersionUID = -8578554704772377436L;
    public static final String ACCEPT = "Accept";
    public static final String ACCEPT_CHARSET = "Accept-Charset";
    public static final String ACCEPT_ENCODING = "Accept-Encoding";
    public static final String ACCEPT_LANGUAGE = "Accept-Language";
    public static final String ACCEPT_PATCH = "Accept-Patch";
    public static final String ACCEPT_RANGES = "Accept-Ranges";
    public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
    public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
    public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
    public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
    public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
    public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
    public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
    public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
    public static final String AGE = "Age";
    public static final String ALLOW = "Allow";
    public static final String AUTHORIZATION = "Authorization";
    public static final String CACHE_CONTROL = "Cache-Control";
    public static final String CONNECTION = "Connection";
    public static final String CONTENT_ENCODING = "Content-Encoding";
    public static final String CONTENT_DISPOSITION = "Content-Disposition";
    public static final String CONTENT_LANGUAGE = "Content-Language";
    public static final String CONTENT_LENGTH = "Content-Length";
    public static final String CONTENT_LOCATION = "Content-Location";
    public static final String CONTENT_RANGE = "Content-Range";
    public static final String CONTENT_TYPE = "Content-Type";
    public static final String COOKIE = "Cookie";
    public static final String DATE = "Date";
    public static final String ETAG = "ETag";
    public static final String EXPECT = "Expect";
    public static final String EXPIRES = "Expires";
    public static final String FROM = "From";
    public static final String HOST = "Host";
    public static final String IF_MATCH = "If-Match";
    public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
    public static final String IF_NONE_MATCH = "If-None-Match";
    public static final String IF_RANGE = "If-Range";
    public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
    public static final String LAST_MODIFIED = "Last-Modified";
    public static final String LINK = "Link";
    public static final String LOCATION = "Location";
    public static final String MAX_FORWARDS = "Max-Forwards";
    public static final String ORIGIN = "Origin";
    public static final String PRAGMA = "Pragma";
    public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
    public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
    public static final String RANGE = "Range";
    public static final String REFERER = "Referer";
    public static final String RETRY_AFTER = "Retry-After";
    public static final String SERVER = "Server";
    public static final String SET_COOKIE = "Set-Cookie";
    public static final String SET_COOKIE2 = "Set-Cookie2";
    public static final String TE = "TE";
    public static final String TRAILER = "Trailer";
    public static final String TRANSFER_ENCODING = "Transfer-Encoding";
    public static final String UPGRADE = "Upgrade";
    public static final String USER_AGENT = "User-Agent";
    public static final String VARY = "Vary";
    public static final String VIA = "Via";
    public static final String WARNING = "Warning";
    public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
    public static final org.springframework.http.HttpHeaders EMPTY = new ReadOnlyHttpHeaders(new LinkedMultiValueMap());
    private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");
    private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS;
    private static final ZoneId GMT;
    private static final DateTimeFormatter DATE_FORMATTER;
    private static final DateTimeFormatter[] DATE_PARSERS;
    final MultiValueMap<String, String> headers;

    public HttpHeaders() {
        this(CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap(8, Locale.ENGLISH)));
    }

    public HttpHeaders(MultiValueMap<String, String> headers) {
        Assert.notNull(headers, "MultiValueMap must not be null");
        this.headers = headers;
    }

    public List<String> getOrEmpty(Object headerName) {
        List<String> values = this.get(headerName);
        return values != null ? values : Collections.emptyList();
    }

    public void setAccept(List<MediaType> acceptableMediaTypes) {
        this.set("Accept", MediaType.toString(acceptableMediaTypes));
    }

    public List<MediaType> getAccept() {
        return MediaType.parseMediaTypes(this.get("Accept"));
    }

    public void setAcceptLanguage(List<LanguageRange> languages) {
        Assert.notNull(languages, "LanguageRange List must not be null");
        DecimalFormat decimal = new DecimalFormat("0.0", DECIMAL_FORMAT_SYMBOLS);
        List<String> values = (List)languages.stream().map((range) -> {
            return range.getWeight() == 1.0D ? range.getRange() : range.getRange() + ";q=" + decimal.format(range.getWeight());
        }).collect(Collectors.toList());
        this.set("Accept-Language", this.toCommaDelimitedString(values));
    }

    public List<LanguageRange> getAcceptLanguage() {
        String value = this.getFirst("Accept-Language");
        return StringUtils.hasText(value) ? LanguageRange.parse(value) : Collections.emptyList();
    }

    public void setAcceptLanguageAsLocales(List<Locale> locales) {
        this.setAcceptLanguage((List)locales.stream().map((locale) -> {
            return new LanguageRange(locale.toLanguageTag());
        }).collect(Collectors.toList()));
    }

    public List<Locale> getAcceptLanguageAsLocales() {
        List<LanguageRange> ranges = this.getAcceptLanguage();
        return ranges.isEmpty() ? Collections.emptyList() : (List)ranges.stream().map((range) -> {
            return Locale.forLanguageTag(range.getRange());
        }).filter((locale) -> {
            return StringUtils.hasText(locale.getDisplayName());
        }).collect(Collectors.toList());
    }

    public void setAcceptPatch(List<MediaType> mediaTypes) {
        this.set("Accept-Patch", MediaType.toString(mediaTypes));
    }

    public List<MediaType> getAcceptPatch() {
        return MediaType.parseMediaTypes(this.get("Accept-Patch"));
    }

    public void setAccessControlAllowCredentials(boolean allowCredentials) {
        this.set("Access-Control-Allow-Credentials", Boolean.toString(allowCredentials));
    }

    public boolean getAccessControlAllowCredentials() {
        return Boolean.parseBoolean(this.getFirst("Access-Control-Allow-Credentials"));
    }

    public void setAccessControlAllowHeaders(List<String> allowedHeaders) {
        this.set("Access-Control-Allow-Headers", this.toCommaDelimitedString(allowedHeaders));
    }

    public List<String> getAccessControlAllowHeaders() {
        return this.getValuesAsList("Access-Control-Allow-Headers");
    }

    public void setAccessControlAllowMethods(List<HttpMethod> allowedMethods) {
        this.set("Access-Control-Allow-Methods", StringUtils.collectionToCommaDelimitedString(allowedMethods));
    }

    public List<HttpMethod> getAccessControlAllowMethods() {
        List<HttpMethod> result = new ArrayList();
        String value = this.getFirst("Access-Control-Allow-Methods");
        if (value != null) {
            String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
            String[] var4 = tokens;
            int var5 = tokens.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                String token = var4[var6];
                HttpMethod resolved = HttpMethod.resolve(token);
                if (resolved != null) {
                    result.add(resolved);
                }
            }
        }

        return result;
    }

    public void setAccessControlAllowOrigin(@Nullable String allowedOrigin) {
        this.setOrRemove("Access-Control-Allow-Origin", allowedOrigin);
    }

    @Nullable
    public String getAccessControlAllowOrigin() {
        return this.getFieldValues("Access-Control-Allow-Origin");
    }

    public void setAccessControlExposeHeaders(List<String> exposedHeaders) {
        this.set("Access-Control-Expose-Headers", this.toCommaDelimitedString(exposedHeaders));
    }

    public List<String> getAccessControlExposeHeaders() {
        return this.getValuesAsList("Access-Control-Expose-Headers");
    }

    public void setAccessControlMaxAge(Duration maxAge) {
        this.set("Access-Control-Max-Age", Long.toString(maxAge.getSeconds()));
    }

    public void setAccessControlMaxAge(long maxAge) {
        this.set("Access-Control-Max-Age", Long.toString(maxAge));
    }

    public long getAccessControlMaxAge() {
        String value = this.getFirst("Access-Control-Max-Age");
        return value != null ? Long.parseLong(value) : -1L;
    }

    public void setAccessControlRequestHeaders(List<String> requestHeaders) {
        this.set("Access-Control-Request-Headers", this.toCommaDelimitedString(requestHeaders));
    }

    public List<String> getAccessControlRequestHeaders() {
        return this.getValuesAsList("Access-Control-Request-Headers");
    }

    public void setAccessControlRequestMethod(@Nullable HttpMethod requestMethod) {
        this.setOrRemove("Access-Control-Request-Method", requestMethod != null ? requestMethod.name() : null);
    }

    @Nullable
    public HttpMethod getAccessControlRequestMethod() {
        return HttpMethod.resolve(this.getFirst("Access-Control-Request-Method"));
    }

    public void setAcceptCharset(List<Charset> acceptableCharsets) {
        StringJoiner joiner = new StringJoiner(", ");
        Iterator var3 = acceptableCharsets.iterator();

        while(var3.hasNext()) {
            Charset charset = (Charset)var3.next();
            joiner.add(charset.name().toLowerCase(Locale.ENGLISH));
        }

        this.set("Accept-Charset", joiner.toString());
    }

    public List<Charset> getAcceptCharset() {
        String value = this.getFirst("Accept-Charset");
        if (value != null) {
            String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
            List<Charset> result = new ArrayList(tokens.length);
            String[] var4 = tokens;
            int var5 = tokens.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                String token = var4[var6];
                int paramIdx = token.indexOf(59);
                String charsetName;
                if (paramIdx == -1) {
                    charsetName = token;
                } else {
                    charsetName = token.substring(0, paramIdx);
                }

                if (!charsetName.equals("*")) {
                    result.add(Charset.forName(charsetName));
                }
            }

            return result;
        } else {
            return Collections.emptyList();
        }
    }

    public void setAllow(Set<HttpMethod> allowedMethods) {
        this.set("Allow", StringUtils.collectionToCommaDelimitedString(allowedMethods));
    }

    public Set<HttpMethod> getAllow() {
        String value = this.getFirst("Allow");
        if (StringUtils.hasLength(value)) {
            String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
            List<HttpMethod> result = new ArrayList(tokens.length);
            String[] var4 = tokens;
            int var5 = tokens.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                String token = var4[var6];
                HttpMethod resolved = HttpMethod.resolve(token);
                if (resolved != null) {
                    result.add(resolved);
                }
            }

            return EnumSet.copyOf(result);
        } else {
            return EnumSet.noneOf(HttpMethod.class);
        }
    }

    public void setBasicAuth(String username, String password) {
        this.setBasicAuth(username, password, (Charset)null);
    }

    public void setBasicAuth(String username, String password, @Nullable Charset charset) {
        this.setBasicAuth(encodeBasicAuth(username, password, charset));
    }

    public void setBasicAuth(String encodedCredentials) {
        Assert.hasText(encodedCredentials, "'encodedCredentials' must not be null or blank");
        this.set("Authorization", "Basic " + encodedCredentials);
    }

    public void setBearerAuth(String token) {
        this.set("Authorization", "Bearer " + token);
    }

    public void setCacheControl(CacheControl cacheControl) {
        this.setOrRemove("Cache-Control", cacheControl.getHeaderValue());
    }

    public void setCacheControl(@Nullable String cacheControl) {
        this.setOrRemove("Cache-Control", cacheControl);
    }

    @Nullable
    public String getCacheControl() {
        return this.getFieldValues("Cache-Control");
    }

    public void setConnection(String connection) {
        this.set("Connection", connection);
    }

    public void setConnection(List<String> connection) {
        this.set("Connection", this.toCommaDelimitedString(connection));
    }

    public List<String> getConnection() {
        return this.getValuesAsList("Connection");
    }

    public void setContentDispositionFormData(String name, @Nullable String filename) {
        Assert.notNull(name, "Name must not be null");
        Builder disposition = ContentDisposition.formData().name(name);
        if (StringUtils.hasText(filename)) {
            disposition.filename(filename);
        }

        this.setContentDisposition(disposition.build());
    }

    public void setContentDisposition(ContentDisposition contentDisposition) {
        this.set("Content-Disposition", contentDisposition.toString());
    }

    public ContentDisposition getContentDisposition() {
        String contentDisposition = this.getFirst("Content-Disposition");
        return StringUtils.hasText(contentDisposition) ? ContentDisposition.parse(contentDisposition) : ContentDisposition.empty();
    }

    public void setContentLanguage(@Nullable Locale locale) {
        this.setOrRemove("Content-Language", locale != null ? locale.toLanguageTag() : null);
    }

    @Nullable
    public Locale getContentLanguage() {
        return (Locale)this.getValuesAsList("Content-Language").stream().findFirst().map(Locale::forLanguageTag).orElse((Locale)null);
    }

    public void setContentLength(long contentLength) {
        this.set("Content-Length", Long.toString(contentLength));
    }

    public long getContentLength() {
        String value = this.getFirst("Content-Length");
        return value != null ? Long.parseLong(value) : -1L;
    }

    public void setContentType(@Nullable MediaType mediaType) {
        if (mediaType != null) {
            Assert.isTrue(!mediaType.isWildcardType(), "Content-Type cannot contain wildcard type '*'");
            Assert.isTrue(!mediaType.isWildcardSubtype(), "Content-Type cannot contain wildcard subtype '*'");
            this.set("Content-Type", mediaType.toString());
        } else {
            this.remove("Content-Type");
        }

    }

    @Nullable
    public MediaType getContentType() {
        String value = this.getFirst("Content-Type");
        return StringUtils.hasLength(value) ? MediaType.parseMediaType(value) : null;
    }

    public void setDate(ZonedDateTime date) {
        this.setZonedDateTime("Date", date);
    }

    public void setDate(Instant date) {
        this.setInstant("Date", date);
    }

    public void setDate(long date) {
        this.setDate("Date", date);
    }

    public long getDate() {
        return this.getFirstDate("Date");
    }

    public void setETag(@Nullable String etag) {
        if (etag != null) {
            Assert.isTrue(etag.startsWith("\"") || etag.startsWith("W/"), "Invalid ETag: does not start with W/ or \"");
            Assert.isTrue(etag.endsWith("\""), "Invalid ETag: does not end with \"");
            this.set("ETag", etag);
        } else {
            this.remove("ETag");
        }

    }

    @Nullable
    public String getETag() {
        return this.getFirst("ETag");
    }

    public void setExpires(ZonedDateTime expires) {
        this.setZonedDateTime("Expires", expires);
    }

    public void setExpires(Instant expires) {
        this.setInstant("Expires", expires);
    }

    public void setExpires(long expires) {
        this.setDate("Expires", expires);
    }

    public long getExpires() {
        return this.getFirstDate("Expires", false);
    }

    public void setHost(@Nullable InetSocketAddress host) {
        if (host != null) {
            String value = host.getHostString();
            int port = host.getPort();
            if (port != 0) {
                value = value + ":" + port;
            }

            this.set("Host", value);
        } else {
            this.remove("Host", (Object)null);
        }

    }

    @Nullable
    public InetSocketAddress getHost() {
        String value = this.getFirst("Host");
        if (value == null) {
            return null;
        } else {
            String host = null;
            int port = 0;
            int separator = value.startsWith("[") ? value.indexOf(58, value.indexOf(93)) : value.lastIndexOf(58);
            if (separator != -1) {
                host = value.substring(0, separator);
                String portString = value.substring(separator + 1);

                try {
                    port = Integer.parseInt(portString);
                } catch (NumberFormatException var7) {
                }
            }

            if (host == null) {
                host = value;
            }

            return InetSocketAddress.createUnresolved(host, port);
        }
    }

    public void setIfMatch(String ifMatch) {
        this.set("If-Match", ifMatch);
    }

    public void setIfMatch(List<String> ifMatchList) {
        this.set("If-Match", this.toCommaDelimitedString(ifMatchList));
    }

    public List<String> getIfMatch() {
        return this.getETagValuesAsList("If-Match");
    }

    public void setIfModifiedSince(ZonedDateTime ifModifiedSince) {
        this.setZonedDateTime("If-Modified-Since", ifModifiedSince.withZoneSameInstant(GMT));
    }

    public void setIfModifiedSince(Instant ifModifiedSince) {
        this.setInstant("If-Modified-Since", ifModifiedSince);
    }

    public void setIfModifiedSince(long ifModifiedSince) {
        this.setDate("If-Modified-Since", ifModifiedSince);
    }

    public long getIfModifiedSince() {
        return this.getFirstDate("If-Modified-Since", false);
    }

    public void setIfNoneMatch(String ifNoneMatch) {
        this.set("If-None-Match", ifNoneMatch);
    }

    public void setIfNoneMatch(List<String> ifNoneMatchList) {
        this.set("If-None-Match", this.toCommaDelimitedString(ifNoneMatchList));
    }

    public List<String> getIfNoneMatch() {
        return this.getETagValuesAsList("If-None-Match");
    }

    public void setIfUnmodifiedSince(ZonedDateTime ifUnmodifiedSince) {
        this.setZonedDateTime("If-Unmodified-Since", ifUnmodifiedSince.withZoneSameInstant(GMT));
    }

    public void setIfUnmodifiedSince(Instant ifUnmodifiedSince) {
        this.setInstant("If-Unmodified-Since", ifUnmodifiedSince);
    }

    public void setIfUnmodifiedSince(long ifUnmodifiedSince) {
        this.setDate("If-Unmodified-Since", ifUnmodifiedSince);
    }

    public long getIfUnmodifiedSince() {
        return this.getFirstDate("If-Unmodified-Since", false);
    }

    public void setLastModified(ZonedDateTime lastModified) {
        this.setZonedDateTime("Last-Modified", lastModified.withZoneSameInstant(GMT));
    }

    public void setLastModified(Instant lastModified) {
        this.setInstant("Last-Modified", lastModified);
    }

    public void setLastModified(long lastModified) {
        this.setDate("Last-Modified", lastModified);
    }

    public long getLastModified() {
        return this.getFirstDate("Last-Modified", false);
    }

    public void setLocation(@Nullable URI location) {
        this.setOrRemove("Location", location != null ? location.toASCIIString() : null);
    }

    @Nullable
    public URI getLocation() {
        String value = this.getFirst("Location");
        return value != null ? URI.create(value) : null;
    }

    public void setOrigin(@Nullable String origin) {
        this.setOrRemove("Origin", origin);
    }

    @Nullable
    public String getOrigin() {
        return this.getFirst("Origin");
    }

    public void setPragma(@Nullable String pragma) {
        this.setOrRemove("Pragma", pragma);
    }

    @Nullable
    public String getPragma() {
        return this.getFirst("Pragma");
    }

    public void setRange(List<HttpRange> ranges) {
        String value = HttpRange.toString(ranges);
        this.set("Range", value);
    }

    public List<HttpRange> getRange() {
        String value = this.getFirst("Range");
        return HttpRange.parseRanges(value);
    }

    public void setUpgrade(@Nullable String upgrade) {
        this.setOrRemove("Upgrade", upgrade);
    }

    @Nullable
    public String getUpgrade() {
        return this.getFirst("Upgrade");
    }

    public void setVary(List<String> requestHeaders) {
        this.set("Vary", this.toCommaDelimitedString(requestHeaders));
    }

    public List<String> getVary() {
        return this.getValuesAsList("Vary");
    }

    public void setZonedDateTime(String headerName, ZonedDateTime date) {
        this.set(headerName, DATE_FORMATTER.format(date));
    }

    public void setInstant(String headerName, Instant date) {
        this.setZonedDateTime(headerName, ZonedDateTime.ofInstant(date, GMT));
    }

    public void setDate(String headerName, long date) {
        this.setInstant(headerName, Instant.ofEpochMilli(date));
    }

    public long getFirstDate(String headerName) {
        return this.getFirstDate(headerName, true);
    }

    private long getFirstDate(String headerName, boolean rejectInvalid) {
        ZonedDateTime zonedDateTime = this.getFirstZonedDateTime(headerName, rejectInvalid);
        return zonedDateTime != null ? zonedDateTime.toInstant().toEpochMilli() : -1L;
    }

    @Nullable
    public ZonedDateTime getFirstZonedDateTime(String headerName) {
        return this.getFirstZonedDateTime(headerName, true);
    }

    @Nullable
    private ZonedDateTime getFirstZonedDateTime(String headerName, boolean rejectInvalid) {
        String headerValue = this.getFirst(headerName);
        if (headerValue == null) {
            return null;
        } else {
            if (headerValue.length() >= 3) {
                int parametersIndex = headerValue.indexOf(59);
                if (parametersIndex != -1) {
                    headerValue = headerValue.substring(0, parametersIndex);
                }

                DateTimeFormatter[] var5 = DATE_PARSERS;
                int var6 = var5.length;
                int var7 = 0;

                while(var7 < var6) {
                    DateTimeFormatter dateFormatter = var5[var7];

                    try {
                        return ZonedDateTime.parse(headerValue, dateFormatter);
                    } catch (DateTimeParseException var10) {
                        ++var7;
                    }
                }
            }

            if (rejectInvalid) {
                throw new IllegalArgumentException("Cannot parse date value \"" + headerValue + "\" for \"" + headerName + "\" header");
            } else {
                return null;
            }
        }
    }

    public List<String> getValuesAsList(String headerName) {
        List<String> values = this.get(headerName);
        if (values != null) {
            List<String> result = new ArrayList();
            Iterator var4 = values.iterator();

            while(var4.hasNext()) {
                String value = (String)var4.next();
                if (value != null) {
                    Collections.addAll(result, StringUtils.tokenizeToStringArray(value, ","));
                }
            }

            return result;
        } else {
            return Collections.emptyList();
        }
    }

    public void clearContentHeaders() {
        this.headers.remove("Content-Disposition");
        this.headers.remove("Content-Encoding");
        this.headers.remove("Content-Language");
        this.headers.remove("Content-Length");
        this.headers.remove("Content-Location");
        this.headers.remove("Content-Range");
        this.headers.remove("Content-Type");
    }

    protected List<String> getETagValuesAsList(String headerName) {
        List<String> values = this.get(headerName);
        if (values == null) {
            return Collections.emptyList();
        } else {
            List<String> result = new ArrayList();
            Iterator var4 = values.iterator();

            String value;
            do {
                do {
                    if (!var4.hasNext()) {
                        return result;
                    }

                    value = (String)var4.next();
                } while(value == null);

                Matcher matcher = ETAG_HEADER_VALUE_PATTERN.matcher(value);

                while(matcher.find()) {
                    if ("*".equals(matcher.group())) {
                        result.add(matcher.group());
                    } else {
                        result.add(matcher.group(1));
                    }
                }
            } while(!result.isEmpty());

            throw new IllegalArgumentException("Could not parse header '" + headerName + "' with value '" + value + "'");
        }
    }

    @Nullable
    protected String getFieldValues(String headerName) {
        List<String> headerValues = this.get(headerName);
        return headerValues != null ? this.toCommaDelimitedString(headerValues) : null;
    }

    protected String toCommaDelimitedString(List<String> headerValues) {
        StringJoiner joiner = new StringJoiner(", ");
        Iterator var3 = headerValues.iterator();

        while(var3.hasNext()) {
            String val = (String)var3.next();
            if (val != null) {
                joiner.add(val);
            }
        }

        return joiner.toString();
    }

    private void setOrRemove(String headerName, @Nullable String headerValue) {
        if (headerValue != null) {
            this.set(headerName, headerValue);
        } else {
            this.remove(headerName);
        }

    }

    @Nullable
    public String getFirst(String headerName) {
        return (String)this.headers.getFirst(headerName);
    }

    public void add(String headerName, @Nullable String headerValue) {
        this.headers.add(headerName, headerValue);
    }

    public void addAll(String key, List<? extends String> values) {
        this.headers.addAll(key, values);
    }

    public void addAll(MultiValueMap<String, String> values) {
        this.headers.addAll(values);
    }

    public void set(String headerName, @Nullable String headerValue) {
        this.headers.set(headerName, headerValue);
    }

    public void setAll(Map<String, String> values) {
        this.headers.setAll(values);
    }

    public Map<String, String> toSingleValueMap() {
        return this.headers.toSingleValueMap();
    }

    public int size() {
        return this.headers.size();
    }

    public boolean isEmpty() {
        return this.headers.isEmpty();
    }

    public boolean containsKey(Object key) {
        return this.headers.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return this.headers.containsValue(value);
    }

    @Nullable
    public List<String> get(Object key) {
        return (List)this.headers.get(key);
    }

    public List<String> put(String key, List<String> value) {
        return (List)this.headers.put(key, value);
    }

    public List<String> remove(Object key) {
        return (List)this.headers.remove(key);
    }

    public void putAll(Map<? extends String, ? extends List<String>> map) {
        this.headers.putAll(map);
    }

    public void clear() {
        this.headers.clear();
    }

    public Set<String> keySet() {
        return this.headers.keySet();
    }

    public Collection<List<String>> values() {
        return this.headers.values();
    }

    public Set<Entry<String, List<String>>> entrySet() {
        return this.headers.entrySet();
    }

    public boolean equals(@Nullable Object other) {
        if (this == other) {
            return true;
        } else {
            return !(other instanceof org.springframework.http.HttpHeaders) ? false : unwrap(this).equals(unwrap((org.springframework.http.HttpHeaders)other));
        }
    }

    private static MultiValueMap<String, String> unwrap(org.springframework.http.HttpHeaders headers) {
        while(headers.headers instanceof org.springframework.http.HttpHeaders) {
            headers = (org.springframework.http.HttpHeaders)headers.headers;
        }

        return headers.headers;
    }

    public int hashCode() {
        return this.headers.hashCode();
    }

    public String toString() {
        return formatHeaders(this.headers);
    }

    public static org.springframework.http.HttpHeaders readOnlyHttpHeaders(MultiValueMap<String, String> headers) {
        return (org.springframework.http.HttpHeaders)(headers instanceof org.springframework.http.HttpHeaders ? readOnlyHttpHeaders((org.springframework.http.HttpHeaders)headers) : new ReadOnlyHttpHeaders(headers));
    }

    public static org.springframework.http.HttpHeaders readOnlyHttpHeaders(org.springframework.http.HttpHeaders headers) {
        Assert.notNull(headers, "HttpHeaders must not be null");
        return (org.springframework.http.HttpHeaders)(headers instanceof ReadOnlyHttpHeaders ? headers : new ReadOnlyHttpHeaders(headers.headers));
    }

    public static org.springframework.http.HttpHeaders writableHttpHeaders(org.springframework.http.HttpHeaders headers) {
        Assert.notNull(headers, "HttpHeaders must not be null");
        if (headers == EMPTY) {
            return new org.springframework.http.HttpHeaders();
        } else {
            return headers instanceof ReadOnlyHttpHeaders ? new org.springframework.http.HttpHeaders(headers.headers) : headers;
        }
    }

    public static String formatHeaders(MultiValueMap<String, String> headers) {
        return (String)headers.entrySet().stream().map((entry) -> {
            List<String> values = (List)entry.getValue();
            return (String)entry.getKey() + ":" + (values.size() == 1 ? "\"" + (String)values.get(0) + "\"" : (String)values.stream().map((s) -> {
                return "\"" + s + "\"";
            }).collect(Collectors.joining(", ")));
        }).collect(Collectors.joining(", ", "[", "]"));
    }

    public static String encodeBasicAuth(String username, String password, @Nullable Charset charset) {
        Assert.notNull(username, "Username must not be null");
        Assert.doesNotContain(username, ":", "Username must not contain a colon");
        Assert.notNull(password, "Password must not be null");
        if (charset == null) {
            charset = StandardCharsets.ISO_8859_1;
        }

        CharsetEncoder encoder = charset.newEncoder();
        if (encoder.canEncode(username) && encoder.canEncode(password)) {
            String credentialsString = username + ":" + password;
            byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(charset));
            return new String(encodedBytes, charset);
        } else {
            throw new IllegalArgumentException("Username or password contains characters that cannot be encoded to " + charset.displayName());
        }
    }

    static String formatDate(long date) {
        Instant instant = Instant.ofEpochMilli(date);
        ZonedDateTime time = ZonedDateTime.ofInstant(instant, GMT);
        return DATE_FORMATTER.format(time);
    }

    static {
        DECIMAL_FORMAT_SYMBOLS = new DecimalFormatSymbols(Locale.ENGLISH);
        GMT = ZoneId.of("GMT");
        DATE_FORMATTER = DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US).withZone(GMT);
        DATE_PARSERS = new DateTimeFormatter[]{DateTimeFormatter.RFC_1123_DATE_TIME, DateTimeFormatter.ofPattern("EEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss yyyy", Locale.US).withZone(GMT)};
    }
}
